﻿' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Runtime.CompilerServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
    Friend Module TypeBlockSyntaxExtensions
        <Extension>
        Public Function GetInsertionIndices(destination As TypeBlockSyntax,
                                            cancellationToken As CancellationToken) As IList(Of Boolean)
            Dim members = destination.Members

            Dim indices = New List(Of Boolean)
            If members.Count = 0 Then
                Dim start = destination.BlockStatement.Span.End
                Dim [end] = destination.EndBlockStatement.SpanStart

                indices.Add(Not destination.OverlapsHiddenPosition(destination.BlockStatement, destination.EndBlockStatement, cancellationToken))
            Else
                ' First, see if we can insert between the start of the typeblock, and it's first
                ' member.
                indices.Add(Not destination.OverlapsHiddenPosition(destination.BlockStatement, destination.Members.First, cancellationToken))

                ' Now, walk between each member and see if something can be inserted between it and
                ' the next member
                For i = 0 To members.Count - 2
                    Dim member1 = members(i)
                    Dim member2 = members(i + 1)

                    indices.Add(Not destination.OverlapsHiddenPosition(member1, member2, cancellationToken))
                Next

                ' Last, see if we can insert between the last member and the end of the typeblock
                indices.Add(Not destination.OverlapsHiddenPosition(destination.Members.Last, destination.EndBlockStatement, cancellationToken))
            End If

            Return indices
        End Function

        Private Function ReplaceTrailingColonToEndOfLineTrivia(Of TNode As SyntaxNode)(node As TNode) As TNode
            Return node.WithTrailingTrivia(node.GetTrailingTrivia().Select(Function(t) If(t.Kind = SyntaxKind.ColonTrivia, SyntaxFactory.ElasticCarriageReturnLineFeed, t)))
        End Function

        Private Function EnsureProperList(Of TSyntax As SyntaxNode)(list As SyntaxList(Of TSyntax)) As SyntaxList(Of TSyntax)
            Dim allElements = list
            If Not allElements.Last().GetTrailingTrivia().Any(Function(t) t.Kind = SyntaxKind.EndOfLineTrivia OrElse t.Kind = SyntaxKind.ColonTrivia) Then
                Return SyntaxFactory.SingletonList(Of TSyntax)(
                    allElements.Last().WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed))
            ElseIf allElements.Last().GetTrailingTrivia().Any(Function(t) t.Kind = SyntaxKind.ColonTrivia) Then
                Return SyntaxFactory.List(Of TSyntax)(
                    allElements.Take(allElements.Count - 1).Concat(ReplaceTrailingColonToEndOfLineTrivia(allElements.Last())))
            End If

            Return list
        End Function

        Private Function EnsureProperInherits(destinationType As TypeBlockSyntax) As SyntaxList(Of InheritsStatementSyntax)
            Dim allElements = destinationType.Inherits
            If allElements.Count > 0 AndAlso
               destinationType.Implements.Count = 0 Then
                Return EnsureProperList(destinationType.Inherits)
            End If

            Return destinationType.Inherits
        End Function

        Private Function EnsureProperImplements(destinationType As TypeBlockSyntax) As SyntaxList(Of ImplementsStatementSyntax)
            Dim allElements = destinationType.Implements
            If allElements.Count > 0 Then
                Return EnsureProperList(destinationType.Implements)
            End If

            Return destinationType.Implements
        End Function

        Private Function EnsureProperBegin(destinationType As TypeBlockSyntax) As TypeStatementSyntax
            If destinationType.Inherits.Count = 0 AndAlso
               destinationType.Implements.Count = 0 AndAlso
               destinationType.BlockStatement.GetTrailingTrivia().Any(Function(t) t.Kind = SyntaxKind.ColonTrivia) Then
                Return ReplaceTrailingColonToEndOfLineTrivia(destinationType.BlockStatement)
            End If

            Return destinationType.BlockStatement
        End Function

        Private Function EnsureEndTokens(destinationType As TypeBlockSyntax) As EndBlockStatementSyntax
            If destinationType.EndBlockStatement.IsMissing Then
                Select Case destinationType.Kind
                    Case SyntaxKind.ClassBlock
                        Return SyntaxFactory.EndClassStatement().WithAdditionalAnnotations(Formatter.Annotation)
                    Case SyntaxKind.InterfaceBlock
                        Return SyntaxFactory.EndInterfaceStatement().WithAdditionalAnnotations(Formatter.Annotation)
                    Case SyntaxKind.StructureBlock
                        Return SyntaxFactory.EndStructureStatement().WithAdditionalAnnotations(Formatter.Annotation)
                End Select
            End If

            Return destinationType.EndBlockStatement
        End Function

        <Extension>
        Public Function FixTerminators(destinationType As TypeBlockSyntax) As TypeBlockSyntax
            Return destinationType.WithInherits(EnsureProperInherits(destinationType)).
                                   WithImplements(EnsureProperImplements(destinationType)).
                                   WithBlockStatement(EnsureProperBegin(destinationType)).
                                   WithEndBlockStatement(EnsureEndTokens(destinationType))
        End Function
    End Module
End Namespace
